from django.conf.urls import url
from django.shortcuts import HttpResponse, render, reverse,redirect
from django.utils.safestring import mark_safe
from django.forms import ModelForm
from stark.Page.page import Pagination
from  django.db.models import Q
from django.http import QueryDict
from django.db.models import ForeignKey,ManyToManyField
import copy


class FiledTags(object):

    def __init__(self,link_data,filed_obj,filed_name,request):
        self.link_data = link_data
        self.filed_obj = filed_obj
        self.filed_name = filed_name
        self.request = request

    def __iter__(self):
        # type(self.request.GET) --------> <class 'django.http.request.QueryDict'>
        params = copy.deepcopy(self.request.GET)
        params._mutable = True
        pk,title = None,None
        # 注意,self.request.GET.get(self.filed_name) 得到的是一个字符串，但是pk是一个int类型，所以要做转换
        current_id = int(self.request.GET.get(self.filed_name, 0))
        if params.get(self.filed_name):
            del params[self.filed_name]
            url = "%s?%s"%(self.request.path_info,params.urlencode())
            yield mark_safe("<a href='%s'>ALL(清除当前过滤条件)</a>"%(url))
        else:
            yield mark_safe("<a href='#'>ALL(清除当前过滤条件)</a>")
        # item 得到的是每一个大的元组或者大的列表中的数据项对象,对于元组可以使用索引，对于对象可以使用.pk, 调用item直接调用的是该对象的 __str__方法
        for item in self.link_data:
            if self.filed_obj.choices:
                pk,title = item[0],item[1]
            elif isinstance(self.filed_obj,ForeignKey) or isinstance(self.filed_obj,ManyToManyField):
                pk,title = item.pk,item
            params[self.filed_name] = pk
            url_arg = "%s?%s"%(self.request.path_info,params.urlencode())
            if current_id == pk:
                yield mark_safe("<a href='%s' class='active'>%s</a>"%(url_arg,title))
            else:
                yield mark_safe("<a href='%s'>%s</a>" % (url_arg, title))


# 为字段封装类,获取到字段对象关联的数据
class Filed(object):
    def __init__(self,filed_name,filed_obj):
        self.filed_name = filed_name
        self.filed_obj = filed_obj

    # 获取字段对象关联的数据对象
    def get_link_data(self):
        if isinstance(self.filed_obj,ForeignKey) or isinstance(self.filed_obj,ManyToManyField):
            return self.filed_obj.rel.to.objects.all()
        if self.filed_obj.choices:
            return self.filed_obj.choices
        else:
            pass


# 针对使用stark组件的默认样式类
'''
为分页、表头显示、数据显示进行类的封装
'''

class Show(object):
    # 实例化类对象时的初始化函数
    def __init__(self,model,real_list_display,list_display_link,get_edit_url,get_del_url,search_label,url_filter,request,action_list,parent):
        self.model = model
        self.real_list_display = real_list_display
        self.list_display_link = list_display_link
        self.get_edit_url = get_edit_url
        self.get_del_url = get_del_url
        self.search_label = search_label
        self.url_filter = url_filter
        # 将 Filter的筛选条件传给filter_coditon
        self.filter_condition = None
        # 将模糊查询的筛选条件传给self.condition
        self.condition = self.search(request)
        self.action_list = action_list()
        self.parent = parent
        self.list_filter = parent.list_filter
        self.filter_query = None

    # 模糊查询，确定筛选条件
    def search(self,request):
        '''
            功能：
                利用Q查询的高级用法，确定过滤条件
        '''
        # 模糊查询
        # 获取到用户要查询的关键字
        words = request.GET.get("words")
        condition = Q()
        # 设置查询的条件为或的关系
        condition.connector = "or"
        # 如果制定了查询字段
        if self.search_label:
            if words:
                for filed in self.search_label:
                    # 一定要写成元组的形式
                    condition.children.append((filed + "__contains", words))
        return condition
    '''
        知识点:
            前端调用类的一个方法，只需要通过对象.方法名即可,不需要加括号。
    '''

    # 批量操作
    def action(self,request):
        if request.method == "POST":
            action = request.POST.get("action")
            _selected_action = request.POST.getlist("_selected_action")
            data_list = self.model.objects.filter(pk__in=_selected_action)
            viwe_obj = getattr(self.parent,action)
            viwe_obj(data_list)

    # 显示分页的函数
    def get_page(self,request,filter_confition=None):
        # 获取当前访问表的所有数据
        data_total = self.model.objects.filter(self.condition).filter(filter_confition)
        current_page = request.GET.get("page")
        data_length = data_total.count()
        base_url = request.path_info
        params = request.GET
        per_page = 5
        tag_count = 5
        page = Pagination(current_page, data_length, base_url, params, per_page, tag_count)
        # 进行分页之后的数据才是显示到页面的数据
        data_list = data_total[page.start:page.end]
        # 显示分页标签
        page_html = page.page_html()
        return data_list,page_html,page

    # 显示表头的函数
    def get_header(self):
        # 显示表头名称
        header_list = []
        for i in self.real_list_display:
            if callable(i):
                # 获取函数名：函数名.__name__
                # header_list.append(i.__name__) -----> low版
                header_list.append(i(self, header=True))
            else:
                if i == "__str__":
                    header_list.append(self.model._meta.model_name.upper())
                else:
                    header_list.append(self.model._meta.get_field(i).verbose_name)
        return header_list

    # 显示数据的函数
    def get_data(self,data_list,page,):
        # 显示数据
        ret_data_list = [] #保存显示的数据
        # data_list 为分页之后的当前表的记录对象
        for obj in data_list:
            tmp_list = []
            # 遍历显示的字段，i 指的是每一个字段名称
            # print(self.real_list_display)  ----> [<function Modelstark.checkbox at 0x0000000003BEFB70>, '__str__', <function Modelstark.edit at 0x0000000003BEFA60>, <function Modelstark.delete at 0x0000000003BEFAE8>]
            for i in self.real_list_display:
                # 如果要显示的字段是一个可执行的函数
                if callable(i):
                    val = i(self,obj=obj,page=page)
                else:
                    # 如果当前对象没有 "__str__" 字段则抛出异常，直接 str(obj)
                    try:
                        filed_obj = self.model._meta.get_field(i)
                        tmp = []
                        if isinstance(filed_obj,ManyToManyField):
                            many_data = getattr(obj,i).all()
                            for i in many_data:
                                tmp.append(str(i))
                            val = ",".join(tmp)
                        else:
                            # val 为反射后的value
                            val = getattr(obj, i)
                        # 判断当前的 字段名称 是否在 list_display_link 中，在的话让该字段对应的值作为a标签
                            if i in self.list_display_link:
                                # 在URL后面添加参数
                                qd = QueryDict(mutable=True)
                                qd["filter"] = page.params.urlencode()
                                val = mark_safe("<a href='%s?%s'>%s</a>" % (self.get_edit_url(obj),qd.urlencode(),val))
                    except Exception as e:
                        val = str(obj)
                tmp_list.append(val)
            ret_data_list.append(tmp_list)
        # print("返回的数据",ret_data_list)
        return ret_data_list

    # filter 过滤
    def get_filter(self,request):
        # ret_tmp = []
        params_filter = copy.deepcopy(request.GET)
        params_filter._mutable = True
        for filed_name in self.list_filter:
            # 字段对象，field_name 为字段名
            filed_obj = self.model._meta.get_field(filed_name)
            # 为字段对象实例Filed类对象,用与获取该对象关联的数据
            obj = Filed(filed_name,filed_obj)
            data = FiledTags(obj.get_link_data(),filed_obj,filed_name,request)
            # 因为有三个filter字段，因此会生成三个可迭代对象
            yield data


    # 根据过滤条件进行数据过滤
    def Filter_condition(self,request):
        # 创建Q对象要加 ()
        condition = Q()
        condition.connector = "and"
        for field,val in request.GET.items():
            if field in self.list_filter:
                condition.children.append((field,val))
        return condition


class Modelstark(object):
    list_display = ["__str__"]
    list_display_link = ["__str__"]
    model_form = None
    search_label = []
    list_action = []
    list_filter = []
    # init 方法在实例化类的时候执行

    def __init__(self, model, site):
        self.model = model
        self.site = site
        self.label_model = (self.model._meta.app_label, self.model._meta.model_name)

    # 默认的批量删除函数
    def batch_delete(self,data_list):
        data_list.delete()
    # 为批量删除函数添加一个描述性信息
    batch_delete.desc = "批量删除"
    '''
    功能：
        真正显示的批量操作
    知识点:
        对用户自定义的批量函数进行扩展
    '''
    def action_list(self):
        tmp = []
        tmp.extend(self.list_action)
        tmp.append(Modelstark.batch_delete)
        ret_tmp = []
        for i in tmp:
            ret_tmp.append({"name":i.__name__,"desc":i.desc})
        return ret_tmp

    '''
    功能：
        将显示、编辑、删除、增加页面的url 封装到函数中。
    函数名:
        def get_add_url(self)
        def get_del_url(self,obj)
        def get_edit_url(self,obj)
        def get_list_url(self)
    '''
    def get_add_url(self):
        add_url = reverse("%s_%s_add" % self.label_model, )
        return add_url

    def get_del_url(self,obj):
        del_url = reverse("%s_%s_del" % self.label_model, args=(obj.pk,))
        return del_url

    def get_edit_url(self,obj):
        edit_url = reverse("%s_%s_edit"% self.label_model,args=(obj.pk,))
        return edit_url

    def get_list_url(self):
        list_url = reverse("%s_%s_show"%self.label_model,)
        return list_url
    '''
    功能:
        为显示页面右侧的添加按钮生成动态的URL
    函数名:
        def add_btn(self,obj=None,header=False)
    '''
    def add_btn(self,obj=None,header=False):
        if header:
            pass
        return mark_safe("<a href='%s' type='button' class='btn btn-success btn-lg'>添加</a>"%self.get_add_url())

    '''
        功能:
            将所有自定义的URL参数都放到 filter 键中
     '''
    def url_filter(self,page):
        qd = QueryDict(mutable=True)
        qd["filter"] = page.params.urlencode()
        return qd.urlencode()
    '''
    功能：
        为显示页面生成编辑、删除和复选框的标签
    函数名:
        def edit(self,obj=None,header=False)
        def delete(self,obj=None,header=False)
         def checkbox(self,obj=None,header=False)
    '''
    def edit(self,obj=None,header=False,page=None):
        if header:
            return "操作"
        return mark_safe("<a href='%s?%s''><button class='btn btn-success'>编辑</button></a> <a href='%s?%s''><button class='btn btn-danger'>删除</button></a>"%(self.get_edit_url(obj),self.url_filter(page),self.get_del_url(obj),self.url_filter(page)))

    # def delete(self,obj=None,header=False,page=None):
    #     if header:
    #         return "操作"
    #     return mark_safe("<a href='%s?%s''>删除</a>" %(self.get_del_url(obj),self.url_filter(page)))

    def checkbox(self,obj=None,header=False,page=None):
        if header:
            return mark_safe("<input type='checkbox' id='action-toggle'>")
        return mark_safe("<input type='checkbox' value='%s' name='_selected_action'>"%obj.pk)

    '''
    功能:
        为每一个类生成modelform,用于渲染标签
        判断用户是否定义了model_form，如果定义了则用用户自己的类，没有则用默认的modelForm
        备注：
            显示中文错误提示信息的解决思想是：
                在样式类中定义一个 model_form = None 的静态变量，在 def default_modelform(self) 函数中判断 model_form = None的bool值，若为False，
                则表明用户没有自定义 model_form ,因此走默认的modelForm.
                若model_form 为True，则表明用户对要注册的表定义了modelForm，因此用户会在自己的 XXXConfig类中声明 model_form = 自定义的modelForm，
                此时用用户自定义的 modelForm
    函数名:
        def mdelform(self)
    '''
    def default_modelform(self):
        class modelForm(ModelForm):
            class Meta:
                model = self.model
                fields = "__all__"
        if not self.model_form:
            return modelForm
        else:
            return self.model_form
    '''
       功能:
            显示、编辑、增加、删除4个视图函数
       函数名:
            def show_list(self, request)
            def add_list(self, request)
            def edit_list(self, request,id)
             def del_list(self, request,id)
       '''
    def show_list(self, request):
        if not request.user.pk:
            return redirect(reverse("backendlogin"))
        # 实例化Show()类
        show_obj = Show(self.model,self.real_list_display(),self.list_display_link,self.get_edit_url,self.get_del_url,self.search_label,self.url_filter,request,self.action_list,self)
        # 显示filter
        filter_list = show_obj.get_filter(request)
        # 确定Filter条件
        filter_condition = show_obj.Filter_condition(request)
        # 显示批量操作
        show_obj.action(request)
        # 显示分页
        data_list,page_html,page = show_obj.get_page(request,filter_condition)
        # 显示表头
        header_list = show_obj.get_header()
        # 显示数据
        ret_data_list = show_obj.get_data(data_list,page,)
        # 显示添加按钮
        btn_list = []
        # 使用类.函数 的方式需要传self，但是使用类的对象.方法的方式不需要传self
        val = Modelstark.add_btn(self)
        btn_list.append(val)
        return render(request, "show_list.html", locals())

    def add_list(self, request):
        modelForm = self.default_modelform()
        errors = {}
        if request.method == "POST":
            form = modelForm(request.POST)
            if form.is_valid():
                obj = form.save()
                pop_id = request.GET.get("pop_id")
                if pop_id:
                    ret = {'pk':obj.pk,'text':str(obj),'pop_id':pop_id}
                    import json
                    return render(request,"pop.html",{'ret':json.dumps(ret)})
                else:
                    return redirect(self.get_list_url())
            else:
                return render(request,"add.html",locals())
        form = modelForm()
        return render(request,"add.html",locals())

    def edit_list(self, request,id):
        obj = self.model.objects.filter(id=id).first()
        modelForm = self.default_modelform()
        if request.method == "POST":
            form = modelForm(request.POST,instance=obj)
            if form.is_valid():
                form.save()
                '''
                功能：
                    在点击提交之后跳转至含参数的URL
                知识点:
                    POST请求:
                        提交的数据存放在 request.POST中
                        若点击提交时候的URL中含有参数，则参数保存在request.GET中
                '''
                # 因为给自定义参数设置了 filter 键，因此取出URL参数中 filter的value
                # get 可以自动对 编码的URL参数进行解码
                arg = request.GET.get("filter")
                url = "%s?%s"%(self.get_list_url(),arg)
                return redirect(url)
            else:
                return render(request, "edit.html", locals())
        form = modelForm(instance=obj)
        return render(request, "edit.html", locals())

    def del_list(self, request,id):
        if request.method == "POST":
            self.model.objects.filter(id=id).delete()
            arg = request.GET.get("filter")
            url = "%s?%s" % (self.get_list_url(), arg)
            return redirect(url)
        list_url = self.get_list_url()
        return render(request,"delete.html",locals())
    '''
    功能：
        在显示页面要真正显示的字段，对list_display进行扩展
    函数名：
        def real_list_display(self)
    '''
    def real_list_display(self):
        new_list_display = []
        new_list_display.extend(self.list_display)
        new_list_display.append(Modelstark.edit)
        # new_list_display.insert(0,Modelstark.checkbox)
        return new_list_display
    '''
    功能：
        为没一个注册的model生成增删改查四条URL
    函数名:
        def get_url_func(self)
    '''
    def get_url_func(self):
        tmp = []
        app_name = self.model._meta.app_label
        model_name = self.model._meta.model_name
        ret = (app_name, model_name)
        tmp.append(url("^$", self.show_list, name="%s_%s_show" %ret))
        tmp.append(url("^add/$", self.add_list, name="%s_%s_add" %ret))
        tmp.append(url("^edit/(\d+)/$", self.edit_list, name="%s_%s_edit" %ret))
        tmp.append(url("^del/(\d+)/$", self.del_list, name="%s_%s_del" %ret))
        '''
        在访问URL之前就已经分别为每张表创建了4条URL
        '''
        return tmp




# 创建stark类，实例化Starksite，用于注册model



class Starksite(object):

    def __init__(self):
        # 保存表的注册信息，保存的格式为:以model类为键，以该model实例化样式对象为值
        self._registry = {}

    # 类的注册函数
    def register(self, model, modelconfig=None):
        '''
        实现model的注册
        :param model: 要注册的类
        :param modelconfig: 该注册类的样式对象
        :return: None
        '''

        '''
        判断用户是否自己定义了样式类，如果没有，此时的modelconfig为None，则用默认的ModelStark样式类进行实例化注册；
        若用户自定义了注册类的样式对象，则使用用于自定的样式对象
        '''
        if not modelconfig:
            modelconfig = Modelstark
        # model是每次循环注册的表，self是实例化的Startksite类的实例化对象
        '''
        在对注册的model进行样式实例化的时候，modelconfig类实际上是Modelstark类的实例化，实例化要执行类的__init__函数，init函数的定义为：
            def __init__(self, model, site):
                self.model = model
                self.site = site
                self.label_model = (self.model._meta.app_label, self.model._meta.model_name)
            此时实例化的方式为：类名.__init__() 方式，因此需要传self。
            另外需要注意的是modelconfig(model,self)中的self是哪个类的实例化对象
            当在注册类的时候没有传入制定的样式类，那么这里的self是Modelstark类的实例化，
            但是，当用户传入定制的样式类时，此时的self是modelconfig类的实例化
        '''
        self._registry[model] = modelconfig(model, self)

    def get_urls(self):
        '''
        生成表的一级和二级URL
        :return: tmp
        '''
        tmp = []
        '''
        item():使字典中的键值对以元组的形式显示
        model: 注册的model类
        model_config:该类对应的样式类对象
        app_label:以字符串形式获取类所在的APP
        model_name:以字符串的获取类名
        '''
        for model, model_config in self._registry.items():
            app_label = model._meta.app_label
            model_name = model._meta.model_name
            #  为什么不能去掉None
            u = url("^%s/%s/" % (app_label, model_name), (model_config.get_url_func(), None, None))
            tmp.append(u)
        return tmp
    '''
    把urls函数做成静态方法，urls.py 中执行urls函数不需要加()
    '''
    @property
    def urls(self):
        return self.get_urls(), None, None


# 创建单例对象，在每一个app中的stark文件中调用
site = Starksite()
